/*
 * @Author: wwssaabb
 * @Date: 2021-09-03 16:29:54
 * @LastEditTime: 2021-12-15 10:47:48
 * @FilePath: \handwritten-code\main\bind.js
 */

/* 
  bind和call的区别是，bind返回的是一个函数，需要结果时调用，
  call是直接返回结果。且bind和call都是接收一个一个参数
*/

/* 
  初级：使用了ES6语法，代码简洁 ，兼容性较差

  中级：使用ES5语法，兼容性较好

  高级：ES5+支持new，最复杂
*/

/* 初级 */
Function.prototype.my_bind = function (context, ...arg1) {
  const fn = this //this指在调用bind的函数

  return res_bind = (...arg2) => {
    return fn.apply(context, arg1.concat(arg2))
  }
}
/* 
  初级，不使用call或者apply 
*/
Function.prototype.my_bind = function (context = global, ...arg1) {
  const key = Symbol('key')
  context[key] = this

  return function (...arg2) {
    const res = context[key](...arg1, ...arg2)
    delete context[key]
    return res
  }
}

/* 中级,使用call、apply*/
Function.prototype.my_bind = function (context) {
  //判断context是否为对象，如果不是对象即指向全局对象
  context = typeof context === 'object' || context !== null ? context : global
  var fn = this
  if (typeof fn !== 'function') {
    throw new TypeError('Must accept function')
  }
  var arg1 = [].slice.call(arguments, 1)
  return function () {
    var arg = [].concat.apply(arg1, arguments)
    return fn.apply(context, arg)
  }
}
/* 中级,不使用call、apply*/
// Function.prototype.my_bind=function(context){
//   context=typeof context==='object'||context!==null?context:global
//   console.log(context)
//   var key='__my_bind_key__'
//   context[key]=this
//   var arg1=[].slice.call(arguments,1)

//   return function(){
//     var arg=[].concat.apply(arg1,arguments)
//     console.log(arg)
//     var Fn=function(){
//       return arguments
//     }
//     var _arg=[].concat.call(arg)
//     console.log(_arg)
//     var res=context[key](_arg)
//     return res
//   }
// }

/* 高级,支持new */
// Function.prototype.my_bind = function (context) {
//   context = typeof context !== 'object' && context !== null ? context : window
//   var slice = Array.prototype.slice
//   var arg1 = slice.call(arguments, 1)
//   var fn = this
//   if (typeof fn !== 'function') {
//     throw new TypeError('Must accept function')
//   }

//   function res_fn() {
//     var arg2 = slice.call(arguments)
//     return fn.apply(
//       //如果res_fn在当前调用的函数的原型链上
//       res_fn.prototype.isPrototypeOf(this) ? this : context,
//       arg1.concat(arg2)
//     )
//   }
//   //指向原来函数的原型链
//   res_fn.prototype = fn.prototype
//   return res_fn
// }

//ES5,不用call或apply实现bind
Function.prototype.myBind = function (context = globalThis) {
  //this类型判断
  if (typeof this !== 'function') return new TypeError(`${this} must be a function`)
  //初始化context
  context = typeof context === 'object' && context !== null ? context : globalThis

  const slice = Array.prototype.slice
  const fn = this

  function temp() {}
  const args1 = slice.call(arguments, 1)
  const result = function () {
    //获取完整的参数
    const args2 = slice.call(arguments)
    const args= args1.map((_,index)=>`args1[${index}]`).concat(args2.map((_,index)=>`args2[${index}]`))
    // const key = Symbol('key')
    //context有两种情况，1、new result的情况，此时context应该指向new出来的实例（new优先级大）2、指向原来的context
    context = this instanceof result ? this : context
    context.__fn__ = fn
    //eval生成执行所有参数的函数
    //const evalText = "context.__fn__(" + args.reduce((p, c, index) => p += index === args.length - 1 ? c : c + ',', '') + ")"
    const evalText = "context.__fn__("+args.join(',')+")"
    const res = eval(evalText)
    delete context.__fn__
    return res
  }

  //空函数做中间层避免直接修改this的原型
  temp.prototype = this.prototype
  result.prototype = new temp()
  return result
}


const p1 = {
  name: '张三',
  age: 20,
  say: function (name, age) {
    console.log(`我是${name}，今年${age}岁`)
  }
}

const p2 = {
  name: '李四',
  age: 18,
}

// p1.say.myBind(p2, ...Object.values(p2))('王五', 23)
// p1.say.myBind()('王五', 23)
const p3 = new(p1.say.myBind())('老八', 24)

/*
Function.prototype.myBind = function (context = globalThis, ...args1) {
  //this类型异常
  if (typeof this !== 'function') return new TypeError(`${this} must be a function`)

  //初始化绑定对象
  context = typeof context === 'object' && context !== null ? context : globalThis

  //空函数
  function temp() {}

  const res = function (...args2) {
    //如果调用了new会将this指向实例对象，new绑定优先级大于显示绑定，因此如果使用了new时此时this
    //应该指向res的实例，所以不用修改this指向；反正没有使用new时this应该指向context
    return this.apply(this instanceof res ? this : context, args1.concat(args2))
  }

  //空函数做中间层避免直接修改this原型
  temp.prototype = this.prototype
  res.prototype = new temp()
  return res
}


//ES5 ,使用Array.prototype.slice将argument类数组转成数组
Function.prototype.myBind = function (context = globalThis) {
  //this类型异常
  if (typeof this !== 'function') return new TypeError(`${this} must be a function`)

  //初始化绑定对象
  context = typeof context === 'object' && context !== null ? context : globalThis
  const slice = Array.prototype.slice
  const args1 = slice.call(arguments, 1)

  //空函数
  function temp() {}

  const res = function () {
    const args2 = slice.call(arguments)
    //如果调用了new会将this指向实例对象，new绑定优先级大于显示绑定，因此如果使用了new时此时this
    //应该指向res的实例，所以不用修改this指向；反正没有使用new时this应该指向context
    return this.apply(this instanceof res ? this : context, args1.concat(args2))
  }

  //空函数原型做中间层避免直接修改this原型
  temp.prototype = this.prototype
  res.prototype = new temp()
  return res
}


//ES5,不用call或apply实现bind
Function.prototype.myBind = function (context = globalThis) {
  //this类型判断
  if (typeof this !== 'function') return new TypeError(`${this} must be a function`)
  //初始化context
  context = typeof context === 'object' && context !== null ? context : globalThis

  const slice = Array.prototype.slice

  function temp() {}
  const args1 = slice.call(arguments, 1)
  const result = function () {
    //获取完整的参数
    const args = args1.concat(slice.call(arguments))
    const key = Symbol('key')
    //context有两种情况，1、new result的情况，此时context应该指向new出来的实例（new优先级大）2、指向原来的context
    context = this instanceof result ? this : context
    context[key] = this
    //eval生成执行所有参数的函数
    return eval(`context[key](${args})`)
  }

  //空函数做中间层避免直接修改this的原型
  temp.prototype = this.prototype
  result.prototype = new temp()
  return result
}
*/



//完全版
Function.prototype.myBind=function(context=globalThis){
  context = typeof context==='object'&&context!==null?context:globalThis

  slice=Array.prototype.slice
  fn=this

  //处理外层参数
  const args1=slice.call(arguments,1)

  function temp(){}
  const result =function(){
    //处理内层参数
    const args2=slice.call(arguments)
    //整合全部参数
    const args=args1.map((_,index)=>`args1[${index}]`).concat(args2.map((_,index)=>`args2[${index}]`))

    //调用此时的函数的时候，this有两种指向情况，一种是new出来的对象（优先级最高），一种是原本的this指向
    context = this instanceof result? this:context
    //新建一个属性保存当前的函数,目的是将this绑定到当前的context
    context.__fn__=fn
    //使用eval执行函数并完全展开参数
    let res=eval('context.__fn__('+args.join(',')+')')
    delete context.__fn__
    return res
  }

  temp.prototype=this.prototype
  result.prototype=new temp()
  return result
}